﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using DNT.RavenQA.Core.Extensions;
using DNT.RavenQA.Core.Syndication;

namespace DNT.RavenQA.Core.Models
{
    public class Question : Contribute, ISyndicable
    {
        public static Question New(IQuestionInput input)
        {
            return New(input, () => DateTime.UtcNow);
        }

        public static Question New(IQuestionInput input, Func<DateTime> now)
        {
            return new Question(input, now);
        }

        protected Question() { }

        protected Question(IQuestionInput input)
            : this(input, () => DateTime.UtcNow) { }

        protected Question(IQuestionInput input, Func<DateTime> now)
        {
            Title = input.Title;
            Body = input.Body;
            User = new DenormalizedReference<User>
                       {
                           Id = input.Author.Id,
                           Name = input.Author.Name
                       };
            input.Author.IncrementContributionsCount();
            AddTags(input.Tags);
            PostedOn = now();
        }

        public void AddTags(IEnumerable<String> tags)
        {
            if (tags.IsNull())
                return;
            
            var enumerable = tags as ICollection<String> ?? tags.ToList();

            if (enumerable.IsNotNull())
                _tags.AddRange(enumerable.Where(tag => tag.IsNotNullOrWhiteSpace()));
        }

        public String Title { get; private set; }

        public IEnumerable<String> Tags
        {
            get { return _tags; }
            private set { _tags = new List<String>(value); }
        }
        private List<String> _tags = new List<String>();

        public String ViewsId { get; private set; }

        public void SetAsViewed(Func<ContributeViews> func, User user)
        {
            var views = func();
            EnsureHasViewsId(views);
            views.New(user);
        }

        private void EnsureHasViewsId(ContributeViews views)
        {
            if (ViewsId.IsNotNull() && views.Id != ViewsId)
                throw new InvalidReferenceException();

            if (ViewsId.IsNull())
                ViewsId = views.Id;
        }

        public void AddAnswer(Func<Answer> func)
        {
            AddAnswer(func, () => DateTime.UtcNow);
        }

        public void AddAnswer(Func<Answer> func, Func<DateTime> now)
        {
            func.EnsureIsNotNull("func");
            func();
            IncrementAnswersCount();
            LastUpdateOn = now();
        }

        private void IncrementAnswersCount()
        {
            AnswersCount++;
        }

        public Boolean IsOwner(User user)
        {
            user.EnsureIsNotNull("user");
            return user.Equals(User);
        }

        public void SetAsAnswered(Answer answer, User user)
        {
            SetAsAnswered(answer, user, () => DateTime.UtcNow);
        }

        public void SetAsAnswered(Answer answer, User user, Func<DateTime> now)
        {
            answer.EnsureIsNotNull("answer");
            user.EnsureIsNotNull("user");

            if (Answered)
                throw new AnswerAlreadyAcceptedException();

            if (!IsOwner(user))
                throw new NotAuthorizedException(String.Format("User '{0}' is not the question's owner and cannot mark this question as 'answered'.", user.Name));

            AcceptedAnswerId = answer.Id;
            answer.MarkAsAccepted();
            LastUpdateOn = now();
        }

        public Int32 AnswersCount { get; private set; }

        public Boolean Answered
        {
            get { return AcceptedAnswerId.IsNotNull(); }
        }

        public String AcceptedAnswerId
        {
            get { return _acceptedAnswerId; }
            private set { _acceptedAnswerId = value; }
        }
        private String _acceptedAnswerId;

        public DateTimeOffset LastUpdateOn { get; private set; }

        public void Idratate(SyndicationItem item)
        {
            item.EnsureIsNotNull("item");
            item.Content = new TextSyndicationContent(Body, TextSyndicationContentKind.Html);
            item.Id = Id;
            item.LastUpdatedTime = LastUpdateOn;
            item.PublishDate = PostedOn;
            item.Title = new TextSyndicationContent(Title, TextSyndicationContentKind.Plaintext);
        }
    }
}